/* Copyright (c) 2021, National University of Defense Technology. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bsr_common.h"
#include "bsr_matrix.h"
#include "bsr_matvec.h"

namespace YHAMG
{

BSR_Matrix::BSR_Matrix()
	: ref(0),
	size{0, 0},
	block_size(0),
	rowptr(0),
	colind(0),
	values(0)
{
}

BSR_Matrix::BSR_Matrix(int _n, int _m, int _bsize, int* _rowptr, int* _colind, double* _values, int _ref)
	: ref(_ref),
	size{_n, _m},
	block_size(_bsize),
	rowptr(_rowptr),
	colind(_colind),
	values(_values)
{
}

BSR_Matrix::BSR_Matrix(const BSR_Matrix& A)
	: ref(0),
	size{A.size[0], A.size[1]},
	block_size(A.block_size),
	rowptr(new int[A.size[0] + 1]),
	colind(new int[A.rowptr[A.size[0]]]),
	values(new double[A.rowptr[A.size[0]] * A.block_size * A.block_size])
{
	int* Ap = A.rowptr;
	int* Ai = A.colind;
	double* Av = A.values;

	int block_nnz = block_size * block_size;

	for (int i = 0; i <= size[0]; ++i)
		rowptr[i] = Ap[i];

	for (int j = 0; j < Ap[size[0]]; ++j)
	{
		colind[j] = Ai[j];
		BSRBlockCopy(block_size, Av + j * block_nnz, values + j * block_nnz);
	}
}

BSR_Matrix::BSR_Matrix(BSR_Matrix&& A)
	: ref(A.ref),
	size{A.size[0], A.size[1]},
	block_size(A.block_size),
	rowptr(A.rowptr),
	colind(A.colind),
	values(A.values)
{
	A.ref = 1;
}

BSR_Matrix::~BSR_Matrix()
{
	if (!ref)
	{
		if (rowptr) delete[] rowptr;
		if (colind) delete[] colind;
		if (values) delete[] values;
	}
}

BSR_Matrix& BSR_Matrix::operator=(const BSR_Matrix& A)
{
	Free();

	int* Ap = A.rowptr;
	int* Ai = A.colind;
	double* Av = A.values;

	size[0] = A.size[0];
	size[1] = A.size[1];
	block_size = A.block_size;

	int block_nnz = block_size * block_size;
	rowptr = new int[size[0] + 1];
	colind = new int[Ap[size[0]]];
	values = new double[Ap[size[0]] * block_nnz];

	for (int i = 0; i <= size[0]; ++i)
		rowptr[i] = Ap[i];

	for (int j = 0; j < Ap[size[0]]; ++j)
	{
		colind[j] = Ai[j];
		BSRBlockCopy(block_size, Av + j * block_nnz, values + j * block_nnz);
	}

	return *this;
}

BSR_Matrix& BSR_Matrix::operator=(BSR_Matrix&& A)
{
	Free();

	ref = A.ref;
	size[0] = A.size[0];
	size[1] = A.size[1];
	block_size = A.block_size;
	rowptr = A.rowptr;
	colind = A.colind;
	values = A.values;

	A.ref = 1;

	return *this;
}

void BSR_Matrix::Free()
{
	if (!ref)
	{
		if (rowptr) delete[] rowptr;
		if (colind) delete[] colind;
		if (values) delete[] values;
	}
	size[0] = 0;
	size[1] = 0;
	block_size = 0;
	rowptr = 0;
	colind = 0;
	values = 0;
	ref = 0;
}

void BSR_Matrix::Reference(const BSR_Matrix& A)
{
	if (!ref)
	{
		if (rowptr) delete[] rowptr;
		if (colind) delete[] colind;
		if (values) delete[] values;
	}
	size[0] = A.size[0];
	size[1] = A.size[1];
	block_size = A.block_size;
	rowptr = A.rowptr;
	colind = A.colind;
	values = A.values;
	ref = 1;
}

int BSR_Matrix::InSize() const
{
	return size[1] * block_size;
}

int BSR_Matrix::OutSize() const
{
	return size[0] * block_size;
}

void BSR_Matrix::Apply(const Vector& x, const Vector& y) const
{
	BSR_MatVec(*this, x, y);
}

}